package mware_lib;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * 
 * Communicate with the skeleton to perform 
 * methods and to send raw commands
 *
 */
public class StubSkeletonCom {
	
	/**
	 * 
	 * @param m remote method
	 * @param name of the remote object
	 * @param params of the method
	 * @param hostName of skeleton
	 * @param port of skeleton
	 * @return an object, if the method has a return value, otherwise null
	 * @throws Exception
	 */
	public static Object methodOfObject(Method m, String name, Object[] params, String hostName, int port) throws Exception{
		String cmd = "";
		String res = null;
		System.out.println("--------------");
		
		//build a command for the skeleton
		cmd += "MethodRequest:";
		cmd += name + ":";
		cmd += m.getName() + ":";
		cmd += m.getParameterTypes().length;
		
		if(m.getParameterTypes() != null){
			for (Class<?> c : m.getParameterTypes()) {
				Class<?> newC = c;
				
				if(newC.equals(double.class)){
					newC = Double.class;
				}
				
				cmd += ":" + newC.getName();
			}
			
			if(params != null){
				for (Object p : params) {
					cmd += ":" + p.toString();
				}
			}
			
		}
		
		if( m.getReturnType().equals(void.class) ){	
			System.out.println("SSC: no return type");
			res = sendIt(cmd, false, hostName, port);
			if(res == null){
				return null;
			}else if(res instanceof String){
				System.out.println("SSC: from Skeleton2: "+res);
			}

			String[] splitted = res.split(":");
			if(splitted[0].equals("InvocationTargetException")){
				return res;
			}
			return null;
		}else{
			System.out.println("SSC: return type " + m.getReturnType().getName() );
			//send data to skeleton
			res = sendIt(cmd, true, hostName, port);
			System.out.println("SSC: from Skeleton1: "+res);
			
			if(res == null){
				return null;
			}
			String[] splitted = res.split(":");
			if(splitted[0].equals("InvocationTargetException")){
				return res;
			}

			if(m.getReturnType().equals(double.class)){
				return Double.parseDouble(splitted[1]);
			}else if(m.getReturnType().equals(boolean.class)){
				return Boolean.parseBoolean(splitted[1]);
			}else{
				return splitted[1];
			}
		}
	}
	
	/**
	 * 
	 * @param data the command in string form
	 * @param withAnswer waits for an answer, otherwise no
	 * @param hostName of skeleton
	 * @param port of skeleton
	 * @return the answer of skeleton, if to answer is expected, null is the answer
	 */
	public static String sendIt(String data, boolean withAnswer, String hostName, int port){
		Socket mySock;
		BufferedReader in = null;
		OutputStream out;
		String resu = null;
		try {
			
			System.out.println("connect to "+hostName+":"+port);
			mySock = new Socket(hostName, port);
			mySock.setReuseAddress(true);
	
			in = new BufferedReader(new InputStreamReader(mySock.getInputStream()));
			out = mySock.getOutputStream();
			
//			System.out.println("SendIt: "+(data+"\n") );
			out.write( (data+"\n").getBytes());
			out.flush();
			resu = in.readLine();
			
			if(resu != null){
//				System.out.println("BACK SendIt: "+resu);
				String[] splitted = resu.split(":");
				if(splitted[0].equals("RemoteException")){
					System.out.println("Message from Skeleton: "+splitted[1]);
					return null;
				}
			}
				
			in.close();
			out.close();
			mySock.close();
		
		} catch (UnknownHostException e) {
			e.printStackTrace();
			System.err.println("Host nicht erreichbar");
		} catch (IOException e) {
			System.err.println("Verbindungsprobleme zum Host");
		}
		return resu;
	}

	/**
	 * Checks for Exceptions inside the result String and throws matching RuntimeException.
	 * @param resu String send through StubSkeletonCom
	 */
	public static void handleIfException(String resu) throws RuntimeException{
		if(resu != null){
			String[] splitted = resu.split(":");
			if(splitted[0].equals("InvocationTargetException")){
				try {
					Class<?> c = Class.forName(splitted[1]); // get Exception
					Constructor<?>[] con = c.getConstructors();
					for (Constructor<?> cons : con) {
						if(splitted.length == 3){
							if(cons.getParameterTypes().length == 1){ // in case we got a message for the exception, it should be send too! 
								Object o = cons.newInstance(splitted[2]);
								throw (RuntimeException) o;
							}
						}else{
							if (cons.getParameterTypes().length == 0) {
								Object o = cons.newInstance();
								throw (RuntimeException) o; // throw RuntimeException
							}
						}
					}
					throw new RuntimeException("An Exception occured!"); // no constructor for exception found, but we need to throw the exception ...
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
		}
	}

}
